/*****************************************************
 Copyright @ DusunIOT 2005-2023. All rights reserved.
 File Name: common_serial.c
 Author: yangwenxu
 Version: V1.0
 Date:2023年07月19日 星期三 09时56分05秒
 Description: 串口的收发
 Other: none
 History: Created2023年07月19日 星期三 09时56分05秒 by:yangwenxu
*****************************************************/

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <sys/time.h>
#include <unistd.h>
#include "common_serial.h"


/* @brief        打开串口，并进行配置。
 * @param[in]    devname 设备名字
 * @param[in]    baudrate 波特率     为0则默认配置为115200
 * @param[in]    conf串口的配置信息  为NULL则默认配置为8N1
 * @param[out]   serial 为SERIAL_INFO_T结构体，存储了串口的一些配置信息
 * return        小于0为配置失败，其余为串口描述符
 *               SERIAL_ERROR_INVALID_PARAMETER_VALUE  参数不合法
 *               SERIAL_ERROR_OPEN_ERR                 串口打开失败
 *               SERIAL_ERROR_SET_FLAG_ERR             设置串口状态标记失败
 *               SERIAL_ERROR_NOT_DEV_TYPE             串口不是终端
 *               SERIAL_ERROR_CONFIG_BACKUO_ERR        串口配置信息备份失败（将结果保留，还原设置时可以用到）
 *               SERIAL_ERROR_CONFIG_BACKUO_ERR        串口配置导出失败（用于设置新的参数）
 *               SERIAL_ERROR_CLEAR_CACHE_ERR          串口缓冲区清除失败
 *               SERIAL_ERROR_CONFIG_ERR               串口配置信息失败
 */
RB_INT32 serial_open(SERIAL_INFO_T *serial, RB_INT8 *devname, speed_t baudrate, RB_INT8 *conf)
{
    
    RB_INT32 retval = 0;       /* 接收函数返回值 */
    struct termios Newtermios; /* 用来存储终端参数的结构体 */

    /* 初始化参数 */
    memset(&Newtermios, 0, sizeof(struct termios));
    memset(&(serial->OldTermios), 0, sizeof(struct termios));
    
    if(serial == RB_NULL || devname == RB_NULL)
    {
        SERIAL_ERR("serial Invalid parameter\n");
        return SERIAL_ERROR_INVALID_PARAMETER_VALUE;
    }
    else
    {
    }
    
    /* 将串口地址写入到串口结构体中 */
    if(devname != RB_NULL)
    {
        strncpy(serial->SerialName, devname, SERIAL_SERIALNAME_LEN);
    }
    else
    {   
    }
  
    /* 判断波特率是否为0，如果是0，就配置为默认设置 */
    if(baudrate != 0)
    {
        serial->Baudrate = baudrate;
    }
    else
    {
        serial->Baudrate = SERIAL_BAUDRATE;
    }
    
    /* 判断配置信息是否为NULL，如果是，就默认配置为 8n1 模式 */
    if(conf == RB_NULL)
    {
        serial->Databits = SERIAL_DARABITS;
        serial->Parity = SERIAL_PARITY;
        serial->Stopbits = SERIAL_STOPBITS;
    }
    else
    {
        serial->Databits = atoi(&conf[0]);
        serial->Parity = conf[1];
        serial->Stopbits = atoi(&conf[2]);
    }

    /* 打开串口，并进行配置。
     * O_RDWR  可读写 
     * O_NOCTTY  如果打开的文件为终端机设备时, 则不会将该终端机当成进程控制终端机.
     * O_NONBLOCK 以不可阻断的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中.
     */
    serial->fd = open(serial->SerialName, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if(serial->fd < 0)
    {
        SERIAL_ERR("%s serail open failed: %s\n", serial->SerialName, strerror(errno));
        return SERIAL_ERROR_OPEN_ERR;
    }
    else
    {
    }

    /* 设置文件描述符flag。设置串口状态标记 */
    retval = fcntl(serial->fd,F_SETFL,0);
    if(retval < 0)
    {
        SERIAL_ERR("%s,Fcntl check faile.\n", serial->SerialName);
        return SERIAL_ERROR_SET_FLAG_ERR;
    }
    else
    {
    }
    
    /* 检测设备fd是否是终端，返回1是，0就不是 */
    retval = isatty(serial->fd);
    if(retval == 0)
    {
        SERIAL_ERR("%s isn't a device type\n", serial->SerialName);
        return SERIAL_ERROR_NOT_DEV_TYPE;
    }
    else
    {
    }

    SERIAL_INFO("%s is a device type \n", serial->SerialName);

    /* 设置发送数据的大小 */
    serial->mSend = SERIAL_SENDBITSMAX;

    /*Serial port configuration backup
    * tcgetattr 获取与终端相关的参数，存储在Oldtermios里
    * 成功返回0 失败返回非0
    */
    retval = tcgetattr(serial->fd, &(serial->OldTermios));
    if(retval != 0)
    {
        SERIAL_ERR("%s.Serial port configuration backup errno：%s\n", serial->SerialName, strerror(errno));
        return SERIAL_ERROR_CONFIG_BACKUO_ERR;
    }
    else
    {
    }

    retval = tcgetattr(serial->fd,&Newtermios);
    if(retval != 0)
    {
        SERIAL_ERR("%s.Get termios to Newtermios failure:%s\n", serial->SerialName, strerror(errno));
        return SERIAL_ERROR_CONFIG_BACKUO_ERR;
    }
    else
    {
    }

    /* 修改控制模式，保证程序不会占用串口 */ 
    Newtermios.c_cflag |= CLOCAL;

    /* 启动接收器，能够从串口中读取输入数据 */ 
    Newtermios.c_cflag |= CREAD;

    /* CSIZE字符大小掩码，将与设置databits相关的标致位置零 */ 
    Newtermios.c_cflag &= ~CSIZE;

    /*
     * ICANON: 标准模式
     * ECHO: 回显所输入的字符
     * ECHOE: 如果同时设置了ICANON标志，ERASE字符删除前一个所输入的字符，WERASE删除前一个输入的单词
     * ISIG: 当接收到INTR/QUIT/SUSP/DSUSP字符，生成一个相应的信号
    */
    Newtermios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    
    /*
     * BRKINT: BREAK将会丢弃输入和输出队列中的数据(flush)，并且如果终端为前台进程组的控制终端，则BREAK将会产生一个
     * SIGINT信号发送到这个前台进程组
     * ICRNL: 将输入中的CR转换为NL
     * INPCK: 允许奇偶校验
     * ISTRIP: 剥离第8个bits
     * IXON: 允许输出端的XON/XOF流控
     */
    Newtermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
        
    /* OPOST: 表示处理后输出，按照原始数据输出 */
    Newtermios.c_oflag &= ~(OPOST);

    /* 波特率的设置 */ 
    cfsetispeed(&Newtermios, serial->Baudrate); /* 输入 */
    cfsetospeed(&Newtermios, serial->Baudrate); /* 输出 */


    /* 设置数据位位数
     * 字符长度，取值范围为CS5、CS6、CS7或CS8
     */
    switch(serial->Databits)
    {
        case 5:
                Newtermios.c_cflag |= CS5;
                break;
        case 6:
                Newtermios.c_cflag |= CS6;
                break;
        case 7:
                Newtermios.c_cflag |= CS7;
                break;
        case 8:
                Newtermios.c_cflag |= CS8;
                break;
        default:
                Newtermios.c_cflag |= CS8;
                break;
    }

    /* 设置校验方式 */
    switch(serial->Parity)
    {
        /* 无校验，不区分大小写 */
        case 'n':
        case 'N':
                Newtermios.c_cflag &= ~PARENB;
                Newtermios.c_iflag &= ~INPCK;
                break;
                
        /* 偶校验，不区分大小写 */
        case 'e':
        case 'E':
                Newtermios.c_cflag |= PARENB;
                Newtermios.c_cflag &= ~PARODD;
                Newtermios.c_iflag |= INPCK;
                break;

        /* 奇校验，不区分大小写 */
        case 'o':
        case 'O':
                Newtermios.c_cflag |= PARENB;
                Newtermios.c_cflag |= PARODD;
                Newtermios.c_iflag |= INPCK;
                break;

        /* 默认无校验 */
        default:
                Newtermios.c_cflag &= ~PARENB;
                Newtermios.c_iflag &= ~INPCK;
    }

    /* 设置停止位 */
    switch(serial->Stopbits)
    {
        /* 设置两个停止位 */
        case 2:
                Newtermios.c_cflag |= CSTOPB;  
                break;
                
        /* 停止位为1，则要清除CSTOPB */
        default:
                Newtermios.c_cflag &= ~CSTOPB;  
                break;
    }

    Newtermios.c_cc[VTIME] = 0;  /* 最长等待时间 */
    Newtermios.c_cc[VMIN] = 0;   /* 最小接收字符 */

    /* 清除输入队列，防止垃圾数据对后续操作进行干扰 */
    if(tcflush(serial->fd, TCIFLUSH))
    {
        SERIAL_ERR("Failed to clear the cache: %s\n", strerror(errno));
        return SERIAL_ERROR_CLEAR_CACHE_ERR;
    }

    /* 设置终端的相关参数
     * TCSANOW:不等数据传输完毕就立即改变属性
     */
    retval = tcsetattr(serial->fd, TCSANOW, &Newtermios);
    if(retval != 0)
    {
        SERIAL_ERR("Serial configuration failure:%s\n", strerror(errno));
        return SERIAL_ERROR_CONFIG_ERR;
    }
    SERIAL_INFO("Serial Init Successdully...\n");
    
    return SERIAL_ERROR_OK;
}

/* @brief        关闭串口.并还原串口参数。
 * @param[in]    serial 为SERIAL_INFO_T结构体，存储了串口的一些配置信息
 * @param[out]   无
 * return        SERIAL_ERROR_OK                       清除成功
 *               SERIAL_ERROR_INVALID_PARAMETER_VALUE  参数不合法
 *               SERIAL_ERROR_CLEAR_ALL_IO_DATA_ERR    串口数据清除失败
 *               SERIAL_ERROR_CONFIG_ERR               串口参数还原失败
 */
RB_INT32 serial_close(SERIAL_INFO_T *serial)
{
    RB_INT32 retval = 0; // 接收函数返回值
    
    if(serial == RB_NULL)
    {
        SERIAL_ERR("serial Invalid parameter\n");
        return SERIAL_ERROR_INVALID_PARAMETER_VALUE;
    }
    else
    {
    }

    /* 清空终端未完成的数据输入/输出请求数据 */
    if(tcflush(serial->fd, TCIOFLUSH) != 0)
    {
        SERIAL_ERR("The input and output queues have been flushed failure: %s\n", strerror(errno));
        return SERIAL_ERROR_CLEAR_ALL_IO_DATA_ERR;
    }
    else
    {
    }
    
    SERIAL_INFO("The input and output queues have been flushed\n");

    /* 设置终端的相关参数 */
    retval = tcsetattr(serial->fd, TCSAFLUSH, &(serial->OldTermios));
    if(retval != 0)
    {
        SERIAL_ERR("%s serial set old options failed: %s\n", serial->SerialName, strerror(errno));
        return SERIAL_ERROR_CONFIG_ERR;
    }
    else
    {
    }
    
    /* 关闭串口 */
    close(serial->fd);

    return SERIAL_ERROR_OK;
}

/* @brief        发送函数
 * @param[in]    serial 为SERIAL_INFO_T结构体，存储了串口的一些配置信息
 * @param[in]    sendBuf 要发送的字符串指针
 * @param[in]    sendBufLen 为发送字符串的大小
 * @param[out]   无
 * return        大于0 为最终发送的字节数
 *               SERIAL_ERROR_CONFIG_ERR          参数为 NULL 或 为负
 *               SERIAL_ERROR_SEND_ERR            数据发送失败（或发送数据大小不匹配）
 */
RB_INT32 serial_send(SERIAL_INFO_T *serial, RB_UINT8 *sendBuf, RB_UINT32 sendBufLen)
{
    
    RB_INT8 *ptr = RB_NULL;  /* 指向要发送的数据头 */
    RB_INT8 *end = RB_NULL;  /* 指向要发送的数据尾 */
    RB_INT32 retval = 0;     /* 用来接收函数的返回值 */
    /* jio@2024-01-23 15:43 原因：修复分包发送时返回值错误问题 */
    RB_INT32 total_send = 0;
    
    if(serial == RB_NULL || sendBuf == RB_NULL || sendBufLen <= 0)
    {
        SERIAL_ERR("Invalid parameter.\n");
        return SERIAL_ERROR_INVALID_PARAMETER_VALUE;
    }
    else
    {
    }

    /* 比较要发送的数据和定义发送数据长度比较
     * 要发送的数据比较大，循环发送，直至发送完成
     * 要发送的数据比较小，直接发送
     */
    if(sendBufLen > serial->mSend)
    {
        ptr = sendBuf; /* 指向字符串开头 */
        end = sendBuf + sendBufLen; /* 指向字符串结尾 */

        do{
            /* 检查剩下的数据是否可以一次发送 */
            if(serial->mSend < (end - ptr))
            {
                /* 只发送固定大小的数据 */
                retval = write(serial->fd, ptr, serial->mSend);
                /* 发送失败或者发送的数据大小不匹配 */
                if(retval <= 0 || retval != serial->mSend)
                {
                    SERIAL_ERR("Write to com port[%d] failed:%s\n", serial->fd, strerror(errno));
                    return SERIAL_ERROR_SEND_ERR;
                }
                
                ptr += serial->mSend;
            }
            else 
            {
                retval = write(serial->fd, ptr, (end - ptr));
                if(retval <= 0 || retval != (end - ptr))
                {
                    SERIAL_ERR("Write to com port[%d] failed:%s\n", serial->fd, strerror(errno));
                    return SERIAL_ERROR_SEND_ERR;
                }

                ptr += (end - ptr);
            }

            total_send += retval;
        }while(end > ptr);
    }  
    else 
    {  
        retval = write(serial->fd, sendBuf, sendBufLen);
        if(retval <= 0 || retval != sendBufLen)
        {
            SERIAL_ERR("Write to com port[[%d] failed:%s, tetval:%d\n", serial->fd, strerror(errno), retval);
            return SERIAL_ERROR_SEND_ERR;
        }
        total_send += retval;
    }  

    return total_send;
} 

/* @brief        接收函数
 * @param[in]    fd ： 串口文件描述符
 * @param[in]    recvBuf  ： 要发送的字符串指针
 * @param[in]    recvBufLen ： 为发送字符串的大小
 * @param[in]    timeout ：要等待的ms数，如果为0 则无限等待
 * @param[out]   sendBufLen : 用于输出获取的数据长度
 * return        0 接收到的数据成功
 *               SERIAL_ERROR_TIMEOUT             等待超时
 *               SERIAL_ERROR_TIMEOUT             参数为 NULL 或 为负
 *               SERIAL_ERROR_SELECT_ERR          select接收失败
 *               SERIAL_ERROR_RECV_ERR            read函数接收失败
 *               SERIAL_ERROR_OVERSTEP_BUF_SIZE   超出缓冲区大小
 */
RB_UINT32 serial_recv(RB_INT32 fd, RB_INT8 *recvBuf, RB_UINT32 recvBufLen, RB_INT32 timeout,RB_INT32 tryOneDelayMs, RB_UINT32 *sendBufLen)
{      
    RB_INT32 retval       = 0;      /* 用来接收函数的返回值 */
    RB_UINT32 recvDataLen = 0;      /* 用来记录接收数据的最终大小 */
    RB_UINT8 readCount    = 0;      /* 用于接收数据次数*/
    fd_set rset;                    /* 用于检查可读性 */
    int index             = 0;
    struct timeval timeOut;         /* 进行超时判断，不能无限制等待 */
    RB_UINT8 tmpBuf[1024];          /* 用来存放接收到的数据 */

    if(tryOneDelayMs > 1000)
        tryOneDelayMs = 5;
    /* 参数初始化 */
    memset(&rset,    0x00, sizeof(fd_set));
    memset(&timeOut, 0x00, sizeof(struct timeval));
    memset(tmpBuf,   0x00, sizeof(tmpBuf));
    
    if(RB_NULL == recvBuf || recvBufLen <= 0)
    {  
        SERIAL_ERR("serial_recv Invalid parameter.\n");
        return SERIAL_ERROR_INVALID_PARAMETER_VALUE;
    }
    if(RB_NULL == sendBufLen)
    {
        SERIAL_ERR("push out data is null\n");
        return RB_ERR;
    }

    FD_ZERO(&rset);
    FD_SET(fd,&rset);
    
    /* 判断时间是否为空 */
    if(0 != timeout) 
    {    
        timeOut.tv_sec = (time_t)(timeout / 1000); /* 等待的秒数 */
        timeOut.tv_usec = 0;                       /* 等待的微秒数 */
        retval = select(fd + 1,&rset,RB_NULL,RB_NULL,&timeOut);
    }
    else
    {
        retval = select(fd + 1,&rset,RB_NULL,RB_NULL,RB_NULL);
    }
    
    /* 对结果进行判断 */
    if(retval < 0)
    {
        SERIAL_ERR("Select failed:%s\n",strerror(errno));
        return SERIAL_ERROR_SELECT_ERR;
    }
    else if(retval == 0)
    {
        return SERIAL_ERROR_TIMEOUT;
    }
    else
    {
    }

    if (!FD_ISSET(fd,&rset))
    {
        SERIAL_ERR("Select failed anothoer: %s\n",strerror(errno));
        return SERIAL_ERROR_TIMEOUT;
    }

    do{
        /* 将接收到的数据存放到rbuf中之后转存到sbuf中 */
        retval = read(fd, recvBuf, recvBufLen);
        for(index = 0; index < retval; index++)
        {
            if ((recvDataLen >= recvBufLen) || (recvDataLen >= sizeof(tmpBuf)))
            {
                SERIAL_ERR("recvDataLen = %d out of buf size\n", recvDataLen);
                return SERIAL_ERROR_OVERSTEP_BUF_SIZE;
            }           
            tmpBuf[recvDataLen] = recvBuf[index];
            recvDataLen++;
        }
        readCount++;
        /* 此处延时的作用是确保最后一个字节也能接收到 */
        if (readCount < 3)
        {
            usleep(tryOneDelayMs*1000);
        }
    }while(retval > 0 && readCount < 3); /* 退出循环的条件是接收的数据大小为0 */

    /* 将数据从临时区域转存到recvBuf中 */
    memcpy(recvBuf, tmpBuf, recvDataLen);
    *sendBufLen = recvDataLen;

    return SERIAL_ERROR_OK;
} 
